# 输出变量

在 Flowgram 中，变量是连接各个节点的关键枢纽。一个节点的输出，往往是另一个节点的输入。因此，如何优雅地定义和输出变量，就成了一门必修课。本文将带你探索在 Flowgram 中输出变量的各种姿势。

我们主要将变量分为两类：

- **节点变量**：作用域仅限于单个节点，通常作为该节点的产出，供后续节点使用。
- **全局变量**：作用域贯穿整个流程，任何节点都可以读取和修改，适合存放一些公共状态或配置。

## 输出节点变量

输出节点变量，意味着这个变量和当前节点的生命周期是绑定的。当节点被创建时，变量就诞生了；当节点被删除时，变量也随之消逝。我们通常有两种方式来输出节点变量：

1.  **通过表单配置**：在节点的设置面板（Form）中，通过一些预设的配置或自定义的逻辑来声明输出变量。这种方式非常直观，所见即所得。
2.  **通过 API 调用**：在插件（Plugin）中，通过调用 `getNodeScope` API 来动态地为节点添加、修改或删除变量。这种方式更加灵活，适合处理复杂的、动态的业务场景。

接下来，我们将深入探讨这两种方式的具体用法。

### 方式一：通过表单配置输出

在节点的 `form-meta.ts` 文件中进行配置，是定义节点输出变量最常见的方式。它又可以分为两种玩法：一种是“懒人版”，使用我们内置的物料；另一种是“大神版”，完全自定义。

#### `provideJsonSchemaOutputs` 物料

如果你的节点需要输出的变量，其结构恰好和表单的 `JSON Schema` 结构一致，那么恭喜你，`provideJsonSchemaOutputs` 这个副作用（Effect）物料就是为你量身定做的！它就像一个自动化的“变量搬运工”，能自动将表单中的数据，原封不动地转换为节点的输出变量。

使用起来非常简单，只需要在 `formMeta` 的 `effect` 中加上两行配置即可：

```tsx pure title="form-meta.ts"
import {
  syncVariableTitle,
  provideJsonSchemaOutputs,
} from '@flowgram.ai/form-materials';

export const formMeta = {
  effect: {
    title: syncVariableTitle, // 变量标题自动同步
    outputs: provideJsonSchemaOutputs,
  },
};
```

#### 通过表单副作用自定义输出

`provideJsonSchemaOutputs` 虽然方便，但它只适配 JsonSchema。

如果你想要定义自己的一套 Schema，那么你就需要自定义表单的副作用了。

Flowgram 提供了一个强大的辅助函数 `createEffectFromVariableProvider`，它能帮你轻松地创建一个用于提供变量的副作用。你可以把它想象成一个“变量加工厂”，输入的是表单数据，输出的是你精心加工后的变量。

下面这个例子中，我们为表单的两个字段 `path.to.value` 和 `path.to.value2` 分别创建了输出变量。当用户在表单中填写这两个字段时，`parse` 函数就会被触发，将用户输入的值（`v`）转换成一个标准的变量声明对象。

```tsx pure title="node-registries.ts"
import {
  FlowNodeRegistry,
  createEffectFromVariableProvider,
  ASTFactory,
  type ASTNodeJSON
} from '@flowgram.ai/fixed-layout-editor';

export function createTypeFromValue(value: string): ASTNodeJSON | undefined {
  switch (value) {
    case 'string':
      return ASTFactory.createString();
    case 'number':
      return ASTFactory.createNumber();
    case 'boolean':
      return ASTFactory.createBoolean();
    case 'integer':
      return ASTFactory.createInteger();

    default:
      return;
  }
}

export const nodeRegistries: FlowNodeRegistry[] = [
  {
    type: 'start',
    formMeta: {
      effect: {
        // Create first variable
        // = variableData.setVar('path.to.value', ASTFactory.createVariableDeclaration(parse(v)))
        'path.to.value': createEffectFromVariableProvider({
          // parse form value to variable
          parse(v: string) {
            return {
              meta: {
                title: `Your Output Variable Title`,
              },
              key: `your_variable_global_unique_key_${node.id}`,
              type: createTypeFromValue(v)
            }
          }
        }),
        // Create second variable
        // = variableData.setVar('path.to.value2', ASTFactory.createVariableDeclaration(parse(v)))
        'path.to.value2': createEffectFromVariableProvider({
          // parse form value to variable
          parse(v: string) {
            return {
              meta: {
                title: `Your Output Variable Title 2`,
              },
              key: `your_variable_global_unique_key_${node.id}_2`,
              type: createTypeFromValue(v)
            }
          }
        }),
      },
      render: () => (
       // ...
      )
    },
  }
]

```

### 方式二：使用 `getNodeScope` API

除了在表单中静态配置，我们还可以在插件（Plugin）中，通过 `getNodeScope` API 来获取作用域，从而动态地操作节点的变量。

这种方式赋予了你极高的自由度，你可以在任何时机、任何地点，对节点的变量进行增、删、改、查。

`getNodeScope` 会返回一个节点的变量作用域（Scope）对象，这个对象上挂载了几个核心方法：

- `setVar(variable)`: 设置一个变量。
- `setVar(namespace, variable)`: 在指定的命名空间下设置一个变量。
- `getVar()`: 获取所有变量。
- `getVar(namespace)`: 获取指定命名空间下的变量。
- `clearVar()`: 清空所有变量。
- `clearVar(namespace)`: 清空指定命名空间下的变量。

下面的例子演示了如何在插件的 `onInit`生命周期中，获取开始节点的 `Scope`，并对它的变量进行一系列操作。

```tsx pure title="sync-variable-plugin.tsx"
import {
  FlowDocument,
  definePluginCreator,
  PluginCreator,
  getNodeScope,
} from '@flowgram.ai/fixed-layout-editor';


export const createSyncVariablePlugin: PluginCreator<SyncVariablePluginOptions> =
  definePluginCreator<SyncVariablePluginOptions, FixedLayoutPluginContext>({
    onInit(ctx, options) {
      const startNode = ctx.get(FlowDocument).getNode('start_0');
      const startScope =  getNodeScope(startNode)

      // 1. Set Variable For Start Scope
      startScope.setVar(
        ASTFactory.createVariableDeclaration({
          meta: {
            title: `Your Output Variable Title`,
          },
          key: `your_variable_unique_key`,
          type: ASTFactory.createString(),
        })
      )

      // 2. Create, Update, Read, Delete Variable in namespace_2
      startScope.setVar(
        'namespace_2',
        ASTFactory.createVariableDeclaration({
          meta: {
            title: `Your Output Variable Title 2`,
          },
          key: `your_variable_global_unique_key_2`,
          type: ASTFactory.createString(),
        })
      )

      console.log(startScope.getVar('namespace_2'))

      // 3. Delete Variable in namespace_2
      startScope.clearVar('namespace_2')
    }
  })

```




## 输出全局变量

全局变量就像是整个流程的“共享内存”，任何节点、任何插件都可以访问和修改它。它非常适合用来存储一些贯穿始终的状态，比如用户信息、环境配置等等。

和节点变量类似，我们也有两种主要的方式来获取全局变量的作用域（`GlobalScope`）。

### 方式一：在插件中获取

在插件的上下文中（`ctx`），我们可以直接“注入”`GlobalScope` 的实例：


```tsx pure title="global-variable-plugin.tsx"
import {
  GlobalScope,
  definePluginCreator,
  PluginCreator
} from '@flowgram.ai/fixed-layout-editor';


export const createGlobalVariablePlugin: PluginCreator<SyncVariablePluginOptions> =
  definePluginCreator<SyncVariablePluginOptions, FixedLayoutPluginContext>({
    onInit(ctx, options) {
      const globalScope = ctx.get(GlobalScope)

      globalScope.setVar(
         ASTFactory.createVariableDeclaration({
          meta: {
            title: `Your Output Variable Title`,
          },
          key: `your_variable_global_unique_key`,
          type: ASTFactory.createString(),
        })
      )
    }
  })

```


### 方式二：在 React 组件中获取

如果你想在画布的 React 组件中与全局变量交互，可以使用 `useService` 这个 Hook 来获取 `GlobalScope` 的实例：

```tsx pure title="global-variable-component.tsx"
import {
  GlobalScope,
  useService,
} from '@flowgram.ai/fixed-layout-editor';

function GlobalVariableComponent() {

  const globalScope = useService(GlobalScope)

  // ...

  const handleChange = (v: string) => {
    globalScope.setVar(
      ASTFactory.createVariableDeclaration({
        meta: {
          title: `Your Output Variable Title`,
        },
        key: `your_variable_global_unique_key_${v}`,
        type: ASTFactory.createString(),
      })
    )
  }

  return <Input onChange={handleChange}/>
}

```



### 全局作用域的 API

`GlobalScope` 的 API 设计得和节点作用域（`NodeScope`）几乎一模一样，都提供了 `setVar`、`getVar`、`clearVar` 等方法，并且同样支持命名空间（namespace）。这种一致性设计大大降低了我们的学习成本。

下面是一个在插件中操作全局变量的综合示例：

```tsx pure title="sync-variable-plugin.tsx"
import {
  GlobalScope,
} from '@flowgram.ai/fixed-layout-editor';

// ...

onInit(ctx, options) {
  const globalScope = ctx.get(GlobalScope);

  // 1. Create, Update, Read, Delete Variable in GlobalScope
  globalScope.setVar(
    ASTFactory.createVariableDeclaration({
      meta: {
        title: `Your Output Variable Title`,
      },
      key: `your_variable_global_unique_key`,
      type: ASTFactory.createString(),
    })
  )

  console.log(globalScope.getVar())

  globalScope.clearVar()


  // 2.  Create, Update, Read, Delete Variable in GlobalScope's namespace: 'namespace_1'
    globalScope.setVar(
      'namespace_1',
      ASTFactory.createVariableDeclaration({
        meta: {
          title: `Your Output Variable Title 2`,
        },
        key: `your_variable_global_unique_key_2`,
        type: ASTFactory.createString(),
      })
  )

  console.log(globalScope.getVar('namespace_1'))

  globalScope.clearVar('namespace_1')

  // ...
}
```

详见：[Class: GlobalScope](https://flowgram.ai/auto-docs/editor/classes/GlobalScope.html)
